package lol.clann.minecraft.springboot.plugin.bukkit.impl;

import lol.clann.minecraft.springboot.api.annotation.compatibility.CompatibilityChecked;
import lol.clann.minecraft.springboot.api.bukkit.utils.VectorUtils;
import lol.clann.minecraft.springboot.bukkit.utils.BukkitUtils;
import net.minecraft.server.v1_7_R4.Vec3D;
import org.bukkit.util.NumberConversions;
import org.bukkit.util.Vector;
import org.springframework.stereotype.Service;

import javax.annotation.Nullable;
import java.util.concurrent.ThreadLocalRandom;

/**
 *
 * @author pyz
 * @date 2019/6/7 7:45 AM
 */
@Service
@CompatibilityChecked
public class VectorUtilsImpl_all implements VectorUtils {
    private ThreadLocalRandom r = ThreadLocalRandom.current();

    @Override
    public void adjust(Vector v, double length) {
        adjust(v, length, 0);
    }

    @Override
    public void adjust(Vector v, double length, double spread) {
        double l = v.length();
        v.setX(calc(v.getX(), l, length, spread));
        v.setY(calc(v.getY(), l, length, spread));
        v.setZ(calc(v.getZ(), l, length, spread));
    }

    @Override
    public <T> T toVec3D(Vector vector) {
        if (vector == null) {
            return null;
        }
        return toVec3D(vector.getX(), vector.getY(), vector.getZ());
    }

    @Override
    public <T> T toVec3D(double x, double y, double z) {
        if (BukkitUtils.getBukkitVersionNumber() < 10800) {
            return (T) Vec3D.a(x, y, z);
        } else {
            return (T) new net.minecraft.server.v1_8_R3.Vec3D(x, y, z);
        }
    }

    @Override
    public <T> Vector fromVec3D(T vec3D) {
        if (vec3D == null) {
            return null;
        }
        Vector v = new Vector();
        if (BukkitUtils.getBukkitVersionNumber() < 10900) {
            Vec3D nms = (Vec3D) vec3D;
            v.setX(nms.a);
            v.setY(nms.b);
            v.setZ(nms.c);
        } else {
            net.minecraft.server.v1_9_R2.Vec3D nms = (net.minecraft.server.v1_9_R2.Vec3D) vec3D;
            v.setX(nms.x);
            v.setY(nms.y);
            v.setZ(nms.z);
        }
        return v;
    }


    /**
     * 当vector竖直时,yaw的值为Float.NaN
     * @param vector
     * @return [yaw][pitch]
     */
    @Override
    public float[] getDirection(Vector vector) {
        float[] d = new float[2];
        double x = vector.getX();
        double z = vector.getZ();
        if (x == 0.0D && z == 0.0D) {
            d[1] = (float) (vector.getY() > 0.0D ? -90 : 90);
            d[0] = Float.NaN;
        } else {
            double theta = Math.atan2(-x, z);
            d[0] = (float) Math.toDegrees((theta + 6.283185307179586D) % 6.283185307179586D);
            double x2 = NumberConversions.square(x);
            double z2 = NumberConversions.square(z);
            double xz = Math.sqrt(x2 + z2);
            d[1] = (float) Math.toDegrees(Math.atan(-vector.getY() / xz));
        }
        return d;
    }

    @Override
    public void setDirection(Vector vector, float yaw, float pitch) {
        double length = vector.length();
        vector.setY(-Math.sin(Math.toRadians(pitch)));
        double xz = Math.cos(Math.toRadians(pitch));
        if (xz == 0D) {
            vector.setX(0);
            vector.setZ(0);
        } else {
            vector.setX(-xz * Math.sin(Math.toRadians(yaw)));
            vector.setZ(xz * Math.cos(Math.toRadians(yaw)));
        }
        vector.multiply(length);
    }

    @Override
    public void rotateYaw(Vector vector, float yaw) {
        float[] direction = getDirection(vector);
        float oy = direction[0];
        if (oy == Float.NaN) {
            return;
        }
        yaw = yaw + oy;
        setDirection(vector, yaw, direction[1]);
    }

    /**
     * 垂直旋转
     * 负数向上,正数向下
     *  @param vector
     * @param pitch
     */
    @Override
    public void rotatePitch(Vector vector, float pitch) {
        float[] direction = getDirection(vector);
        float op = direction[1];
        setDirection(vector, direction[0], op);
    }

    /**
     * Returns a new vector with x value equal to the second parameter, along the line between this vector and the
     * passed in vector, or null if not possible.
     *
     * 直线left -> vec的起点坐标为A,终点坐标为B
     * 在AB上找一点(C),使其x坐标为给定的值
     * 若能找到,返回向量A->C
     *
     */
    @Override
    @Nullable
    public Vector getIntermediateWithXValue(Vector left, Vector vec, double x) {
        double dx = vec.getX() - left.getX();
        double dy = vec.getY() - left.getY();
        double dz = vec.getZ() - left.getZ();

        if (dx * dx < 1.0000000116860974E-7D) {
            return null;
        } else {
            double d3 = (x - left.getX()) / dx;
            return d3 >= 0.0D && d3 <= 1.0D ? new Vector(left.getX() + dx * d3, left.getY() + dy * d3, left.getZ() + dz * d3) : null;
        }
    }

    /**
     * Returns a new vector with y value equal to the second parameter, along the line between this vector and the
     * passed in vector, or null if not possible.
     */
    @Override
    @Nullable
    public Vector getIntermediateWithYValue(Vector left, Vector vec, double y) {
        double d0 = vec.getX() - left.getX();
        double d1 = vec.getY() - left.getY();
        double d2 = vec.getZ() - left.getZ();

        if (d1 * d1 < 1.0000000116860974E-7D) {
            return null;
        } else {
            double d3 = (y - left.getY()) / d1;
            return d3 >= 0.0D && d3 <= 1.0D ? new Vector(left.getX() + d0 * d3, left.getY() + d1 * d3, left.getZ() + d2 * d3) : null;
        }
    }

    /**
     * Returns a new vector with z value equal to the second parameter, along the line between this vector and the
     * passed in vector, or null if not possible.
     */
    @Override
    @Nullable
    public Vector getIntermediateWithZValue(Vector left, Vector vec, double z) {
        double d0 = vec.getX() - left.getX();
        double d1 = vec.getY() - left.getY();
        double d2 = vec.getZ() - left.getZ();

        if (d2 * d2 < 1.0000000116860974E-7D) {
            return null;
        } else {
            double d3 = (z - left.getZ()) / d2;
            return d3 >= 0.0D && d3 <= 1.0D ? new Vector(left.getX() + d0 * d3, left.getY() + d1 * d3, left.getZ() + d2 * d3) : null;
        }
    }

    /**
     * The square of the Euclidean distance between this and the specified vector.
     */
    @Override
    public double squareDistanceTo(Vector left, Vector vec) {
        double d0 = vec.getX() - left.getX();
        double d1 = vec.getY() - left.getY();
        double d2 = vec.getZ() - left.getZ();
        return d0 * d0 + d1 * d1 + d2 * d2;
    }

    @Override
    public double squareDistanceTo(Vector left, double xIn, double yIn, double zIn) {
        double d0 = xIn - left.getX();
        double d1 = yIn - left.getY();
        double d2 = zIn - left.getZ();
        return d0 * d0 + d1 * d1 + d2 * d2;
    }

    private double calc(double value, double length, double speed, double spread) {
        if (spread == 0) {
            return (value / length) * speed;
        } else {
            return (value / length + this.r.nextGaussian() * 0.007499999832361937D * (double) spread) * speed;
        }

    }
}
